#!/bin/bash

# Cinder Recovery Standalone App Launcher
APP_DIR="$(cd "$(dirname "$0")/../Resources/app" && pwd)"
BIN_DIR="$(cd "$(dirname "$0")" && pwd)"

# A GUI-launched process (opened from Finder/Dock, not a Terminal) gets a
# minimal $PATH that often doesn't include /opt/homebrew/bin — resolving
# this once, up front, to an absolute path avoids bare `python3` silently
# resolving to a different binary (or nothing at all) depending on how
# the app was launched.
PYTHON_BIN="/opt/homebrew/bin/python3"
[ -x "$PYTHON_BIN" ] || PYTHON_BIN="$(command -v python3)"

# Ensure IPC directory permissions (still used for license storage and as
# a fallback if the HTTP engine below can't be reached)
mkdir -p "$HOME/.cinder_ipc"
chmod 777 "$HOME/.cinder_ipc" 2>/dev/null || true

# /tmp has the sticky bit set, which means only a file's *owner* can
# delete or replace it there. Logging into our own .cinder_ipc dir
# instead (no sticky bit, so any owner's leftover file can always be
# cleared) avoids a stale log from one run silently blocking the next.
LOG_FILE="$HOME/.cinder_ipc/server.log"
rm -f "$LOG_FILE" 2>/dev/null || true

# Start the HTTP scan/recovery engine (server.py) if it isn't already
# running. The window below loads a local file:// page that immediately
# hands off to this server at http://localhost:8080 once it's reachable —
# see the bootstrap script at the top of frontend/index.html for why.
#
# This used to always launch the engine with administrator privileges,
# on the assumption that raw filesystem access needed root. It doesn't,
# for the thing this app is actually used for: scanning a normal mounted
# drive/volume only needs whatever this exact Python binary has been
# granted in System Settings → Privacy & Security → Full Disk Access —
# that's a per-binary grant macOS enforces the same way regardless of
# whether the process is root or not, so running elevated bought nothing
# here except a password/Touch ID prompt on every single launch, one that
# regularly failed to even show up or silently didn't go through — which
# is exactly what "Recovery Engine Unreachable" and "stuck detecting
# drives" kept turning out to be. Root is only genuinely required for
# MODE 2 in server.py — scanning a raw, unmounted block device directly
# (e.g. /dev/disk2) — which is a rare, advanced case; server.py already
# detects that specific PermissionError and reports it clearly rather
# than failing silently, instead of this launcher demanding elevation
# up front for every single launch just in case someone needs that.
#
# We check whether the engine is actually RESPONDING, not just whether a
# process with that name exists, since a hung instance still bound to the
# port would otherwise block every future launch with no way to recover.
# CinderWindow's own startup (its native Swift code, which we have no
# source for) kills any already-running "backend/server.py" process as
# part of its own one-time launch cleanup — confirmed by direct testing:
# starting the engine, then launching CinderWindow, reliably kills that
# engine within a couple seconds; starting the engine *after* CinderWindow
# has already been up a few seconds, it survives indefinitely. That one-
# time (not ongoing) kill on CinderWindow's own startup is what every
# earlier attempt at starting the engine here — elevated or not, before or
# during `exec` below — kept silently losing to, no matter how the launch
# itself was made more reliable. The fix isn't in how we start it; it's in
# not starting it until CinderWindow's own cleanup has already happened.
#
# So: hand off to CinderWindow immediately (no delay to the window
# appearing), and start the engine from an independent backgrounded
# subshell that waits first — it keeps running after `exec` below since
# backgrounded children aren't affected by their parent replacing its own
# process image. The window's own boot splash (frontend/index.html)
# already polls patiently with visible status and a retry button while
# this plays out, so there's nothing to block on here either way.
# `exec` below replaces this process's running image but keeps the exact
# same PID — so capturing it now gives us CinderWindow's own PID for free,
# no separate lookup needed. That matters for the watchdog at the bottom
# of the subshell: quitting the app normally (Cmd-Q, the red button, the
# Dock menu) was found to leave the engine running as an orphaned process
# for up to 10 minutes (server.py's own heartbeat-timeout backstop), since
# the page's pagehide/beforeunload beacon it depends on doesn't reliably
# fire when this specific native WKWebView wrapper's window closes. Direct
# process-exit monitoring doesn't depend on any WebView page lifecycle
# event firing at all — it's just asking the OS "does this PID still
# exist", which is unambiguous — so it closes that gap immediately instead
# of waiting on the heartbeat backstop, which stays in place underneath
# this for the crash case where even this watchdog's own subshell dies.
WINDOW_PID=$$

(
    sleep 3
    for attempt in 1 2; do
        if curl -s -m 2 "http://127.0.0.1:8080/api/drives" > /dev/null 2>&1; then
            break
        fi
        if pgrep -f "backend/server.py" > /dev/null 2>&1; then
            # Bound to the port but not responding — clear it before
            # retrying. No elevation needed: whatever is holding that port
            # was started the same (non-root) way this script starts
            # things, so a plain pkill as ourselves is enough to remove it.
            pkill -9 -f "backend/server.py" 2>/dev/null
            sleep 0.5
        fi
        "$PYTHON_BIN" "$APP_DIR/backend/server.py" > "$LOG_FILE" 2>&1 &
        sleep 2
    done

    while kill -0 "$WINDOW_PID" 2>/dev/null; do
        sleep 2
    done
    pkill -9 -f "backend/server.py" 2>/dev/null
) &

exec "$BIN_DIR/CinderWindow"
